package com.tonyodev.fetch2core;

import ohos.utils.net.Uri;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This interface can be implemented by a class to create a
 * custom Downloader that can be used by Fetch for downloading requests.
 * A custom downloader is great when you want to provide your own
 * client for Fetch to use for downloading. Note: You are responsible for freeing up resources created
 * by this class when it is no longer needed. Also, the methods in this interface can be called
 * by multiple threads. You are also responsible for making it thread safe when appropriate.
 * For an example see
 *
 * @param <R> r
 * @param <T> t
 * @since 2021-05-20
 */
public interface Downloader<T, R> extends Closeable {
    /**
     * This method is called by Fetch before executing a request against the client.
     * Override this method to setup and configure the client for the request.
     *
     * @param client the client
     * @param request the request
     * @return returns any object that makes sense for the specified downloader. can be null.
     */
    public R onPreClientExecute(T client, Downloader.ServerRequest request);

    /**
     * This method is called by Fetch to execute a request against the client.
     * This method is responsible for creating and opening a connection then returning
     * the response back to the Fetch FileDownloader for processing. This method
     * is called on a background thread.
     *
     * @param request The request information for the download.
     * @param interruptMonitor Notifies the downloader that there may be an interruption for the request.
     * If an interruption occurs, the execute method should return quickly.
     * @return Response
     * @throws IOException io fail
     */
    public Downloader.Response execute(ServerRequest request, InterruptMonitor interruptMonitor) throws IOException;

    /**
     * This method is called by Fetch to disconnect the connection for the passed in response.
     * Perform any clean against the passed in response. This method is called on a background thread.
     *
     * @param response Response containing the server response code, headers, connection success, content-length,
     * and input stream if a connection was successful.
     */
    public void disconnect(Response response);

    /**
     * This method is called by Fetch if the FileDownloaderType.Parallel type was set
     * for the download request. Returns the desired slices that the file will be divided in for parallel downloading.
     * If null is returned, Fetch will automatically select an appropriate slicing size based on the content length.
     * This method is called on a background thread.
     *
     * @param request the request information for the download.
     * @param contentLength the total content length in bytes.
     * @return the slicing size for the request file. Can be null.
     */
    public Integer getFileSlicingCount(ServerRequest request, Long contentLength);

    /**
     * This method is called by Fetch to select the FileDownloaderType for each
     * download request. The Default is FileDownloaderType.SEQUENTIAL.
     * This method is called on a background thread.
     *
     * @param request the request information for the download.
     * @param supportedFileDownloaderTypes a set of file downloader types supported by the request.
     * @return the FileDownloaderType.
     */
    public FileDownloaderType getRequestFileDownloaderType(ServerRequest request, Set<FileDownloaderType> supportedFileDownloaderTypes);

    /**
     * This method should be used to verify that the download file Hash matches the
     * passed in Hash returned by the server for the content.
     * This method is called on a background thread.
     * By default this method tires verify using the MD5 hash. If overriding this method,
     * also override getContentHash(responseHeaders: MutableMap<String, List<String>>) method.
     *
     * @param request the request information for the download.
     * @param hash Hash returned by the server for the content
     * @return return true if the hash values match otherwise false. If false is returned,
     * this indicates that the download files is not correct so the download fails.
     */
    public boolean verifyContentHash(ServerRequest request, String hash);

    /**
     * Get the content hash from Server.
     * By default this method returns the MD5 hash returned by the server response or an empty string
     * if the MD5 is not present. If overriding this method, also override the
     * verifyContentHash(request: ServerRequest, hash: String): boolean method.
     *
     * @param responseHeaders List of headers from response
     * @return return hash value returned by the server for the content. If hash information not found,
     * return empty string.
     */
    public String getContentHash(Map<String, List<String>> responseHeaders);

    /**
     * Notifies the downloader of the server response for a request. This method is called on a background thread.
     *
     * @param request The request information for the download.
     * @param response Response containing the server response code, headers, connection success, content-length,
     * and input stream if a connection was successful.
     */
    public void onServerResponse(ServerRequest request, Response response);

    /**
     * Checks with the downloader to see if the HEAD Request Method is supported by the server.
     * If not, GET Request Method will be used to get information from the server. Default is true.
     * This method is called on a background thread.
     *
     * @param request The request information for the download.
     * @return true if HEAD Request Method is supported. Otherwise false
     */
    public boolean getHeadRequestMethodSupported(ServerRequest request);

    /**
     * Attempts to get the ContentLength for a file located at the specified url.
     * This method runs on the calling thread.
     *
     * @param request The request information for the download.
     * @return ContentLength if successful, or -1 if failed.
     */
    public long getRequestContentLength(ServerRequest request);

    /**
     * Attempts to get the buffer size for a specific download.
     * This method runs on a background thread. Note ohos Framework may limit
     * the buffer size of io streams. So a very large buffer size may be limited to 8192
     * by the framework.
     *
     * @param request The request information for the download.
     * @return buffer size or null. If the buffer size is not set. The default
     * buffer size will be 8192 bytes. Can be null.
     */
    public int getRequestBufferSize(ServerRequest request);

    /**
     * Gets a set of supported FileDownloaderTypes for a request.
     *
     * @param request The request information for the download.
     * @return set of supported FileDownloaderTypes
     */
    public Set<FileDownloaderType> getRequestSupportedFileDownloaderTypes(ServerRequest request);

    /**
     * A class that contains the information used by the Downloader to create a connection
     * to the server.
     *
     * @since 2021-06-16
     */
    public static class ServerRequest {
        private final int id;
        @NotNull
        private final String url;
        @NotNull
        private final Map<String, String> headers;
        @NotNull
        private final String file;
        @NotNull
        private final Uri fileUri;
        @Nullable
        private final String tag;
        private final long identifier;
        @NotNull
        private final String requestMethod;
        @NotNull
        private final Extras extras;
        private final boolean redirected;
        @NotNull
        private final String redirectUrl;
        private final int segment;

        /**
         * get id
         *
         * @return id
         */
        public final int getId() {
            return this.id;
        }

        /**
         * get url
         *
         * @return url
         */
        @NotNull
        public final String getUrl() {
            return this.url;
        }

        /**
         * get headers map
         *
         * @return head map
         */
        @NotNull
        public final Map<String, String> getHeaders() {
            return this.headers;
        }

        /**
         * get file
         *
         * @return string file
         */
        @NotNull
        public final String getFile() {
            return this.file;
        }

        @NotNull
        public final Uri getFileUri() {
            return this.fileUri;
        }

        @Nullable
        public final String getTag() {
            return this.tag;
        }

        public final long getIdentifier() {
            return this.identifier;
        }

        @NotNull
        public final String getRequestMethod() {
            return this.requestMethod;
        }

        @NotNull
        public final Extras getExtras() {
            return this.extras;
        }

        public final boolean getRedirected() {
            return this.redirected;
        }

        @NotNull
        public final String getRedirectUrl() {
            return this.redirectUrl;
        }

        public final int getSegment() {
            return this.segment;
        }

        /**
         * 构造函数
         *
         * @param id id
         * @param url url
         * @param headers 头部
         * @param file 文件
         * @param fileUri 文件路径
         * @param tag 标签
         * @param identifier iden
         * @param requestMethod 请求方式
         * @param extras 数据
         * @param redirected 重新传入
         * @param redirectUrl 重定向
         * @param segment 部分
         */
        public ServerRequest(int id, @NotNull String url, @NotNull Map<String, String> headers, @NotNull String file,
                             @NotNull Uri fileUri, @Nullable String tag, long identifier, @NotNull String requestMethod,
                             @NotNull Extras extras, boolean redirected, @NotNull String redirectUrl, int segment) {
            this.id = id;
            this.url = url;
            this.headers = headers;
            this.file = file;
            this.fileUri = fileUri;
            this.tag = tag;
            this.identifier = identifier;
            this.requestMethod = requestMethod;
            this.extras = extras;
            this.redirected = redirected;
            this.redirectUrl = redirectUrl;
            this.segment = segment;
        }
    }

    /**
     * A class that contains the server response information used by Fetch
     * to being the download process.
     *
     * @since 2021-06-16
     */
    public static class Response {
        private final int code;
        private final boolean isSuccessful;
        private final long contentLength;
        private final InputStream byteStream;
        @NotNull
        private final Downloader.ServerRequest request;
        @NotNull
        private final String hash;
        @NotNull
        private final Map<String, List<String>> responseHeaders;
        private final boolean acceptsRanges;
        @Nullable
        private final String errorResponse;

        public final int getCode() {
            return this.code;
        }

        public final boolean isSuccessful() {
            return this.isSuccessful;
        }

        public final long getContentLength() {
            return this.contentLength;
        }

        public final InputStream getByteStream() {
            return this.byteStream;
        }

        @NotNull
        public final Downloader.ServerRequest getRequest() {
            return this.request;
        }

        @NotNull
        public final String getHash() {
            return this.hash;
        }

        @NotNull
        public final Map<String, List<String>> getResponseHeaders() {
            return this.responseHeaders;
        }

        public final boolean getAcceptsRanges() {
            return this.acceptsRanges;
        }

        @Nullable
        public final String getErrorResponse() {
            return this.errorResponse;
        }

        /**
         * 构造函数
         *
         * @param code code
         * @param isSuccessful 是否成功
         * @param contentLength 内容长度
         * @param byteStream 字节流
         * @param request 请求
         * @param hash hash
         * @param responseHeaders 返回头
         * @param acceptsRanges 接收范围
         * @param errorResponse 错误返回
         */
        public Response(int code, boolean isSuccessful, long contentLength, @Nullable InputStream byteStream, @NotNull Downloader.ServerRequest request, @NotNull String hash, @NotNull Map<String, List<String>> responseHeaders, boolean acceptsRanges, @Nullable String errorResponse) {
            this.code = code;
            this.isSuccessful = isSuccessful;
            this.contentLength = contentLength;
            this.byteStream = byteStream;
            this.request = request;
            this.hash = hash;
            this.responseHeaders = responseHeaders;
            this.acceptsRanges = acceptsRanges;
            this.errorResponse = errorResponse;
        }
    }

    /**
     * File Downloading Type used to download each request.
     *
     * @since 2021-06-16
     */
    enum FileDownloaderType {
        /**
         * Performs the download sequentially. Bytes are downloaded in sequence.
         */
        SEQUENTIAL,

        /**
         * Performs the download by splitting parts of the file in parallel for download.
         * Fastest download option
         */
        PARALLEL
    }
}
